package org.xtext.runtime.eis;

import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.xtext.runtime.formula.Predicate;
import org.xtext.runtime.interpreter.ActionParam;
import org.xtext.runtime.library.Library;
import org.xtext.runtime.term.ITerm;
import org.xtext.runtime.term.Term;
import eis.exceptions.ActException;
import eis.exceptions.ManagementException;
import eis.exceptions.NoEnvironmentException;
import eis.exceptions.PerceiveException;
import eis.iilang.Identifier;
import eis.iilang.Numeral;
import eis.iilang.Parameter;
import eis.iilang.Percept;

public class EIS extends Library {
	EISService service;
	List<String> associations = new LinkedList<String>();
	
	@ACTION
	public boolean setup(String id) {
		service = EISService.getService(id);
		return true;
	}

	@ACTION
	public boolean setup(String id, String jar) {
		service = EISService.getService(id, jar);
		
		if (service.getEnvironmentState().equals("initializing")) {
			try {
				service.eisInit(new HashMap<String, Parameter>());
			} catch (ManagementException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		return true;
	}

	@ACTION
	public boolean setup(String id, String jar, List<String> params) {
		service = EISService.getService(id, jar);

		if (service.getEnvironmentState().equals("initializing")) {
			Map<String, Parameter> parameters = new HashMap<String, Parameter>();
			for (String value : params) {
				System.out.println(value);

//				parameters.put(f.functor(), Utilities.toParameter(f.termAt(0)));
			}
			try {
				service.eisInit(parameters);
			} catch (ManagementException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return false;
			}
		}
		
		return true;
	}

	@ACTION
	public boolean getFreeEntities(ActionParam<List<String>> entities) {
		List<String> list = new LinkedList<String>();
		
		for (String entity : service.getFreeEntities()) {
			list.add(entity);
		}
		
		entities.set(list);
		return true;
	}

	@ACTION
	public boolean getEntityType(String name, ActionParam<String> type) {
		String t = service.queryEntityType(name);
		if (t == null) return false;
		type.set(t);
		return true;
	}
	
	@ACTION
	public boolean start() {
		if (!service.eisStart()) {
			System.out.println("Failed to start EIS Environment...");
			return false;
		}
		return true;
	}

	@ACTION
	public boolean registerAgent() {
		service.registerAgent(agent);
		return true;
	}
	
	@ACTION
	public boolean associateWithEntity(String entity) {
		if (!service.associateEntity(agent, entity)) {
			return false;
		}
		associations.add(entity);
		return true;
	}
	
	@ACTION
	public boolean perform(String action) {
		return perform(action, new LinkedList<Object>());
	}
	
	@ACTION 
	public boolean perform(String action, List<?> parameters) {
		if (associations.size() > 1) {
			agent.addBelief(new Predicate("ambiguousAction", new ITerm[] { Term.newInstance(action) } ));
			return false;
		}

		return perform(agent.name(), action, parameters);
	}
	
	@ACTION 
	public boolean perform(String entity, String action) {
		return perform(entity, action, new LinkedList<Object>());
	}
	
	@ACTION 
	public boolean perform(String entity, String action, List<?> parameters) {
		
		// Convert parameter list to EIS parameters types
		LinkedList<Parameter> list = new LinkedList<Parameter>();
		for (int i=0; i<parameters.size(); i++) {
			Object term = parameters.get(i);
			if (Number.class.isInstance(term)) {
				list.add(new Numeral((Number) term));
			} else {
				list.add(new Identifier(term.toString()));
			}
		}
		
		// Invoke the action using EIS
		try {
			Map<String, Percept> percepts = service.performAction(entity, new eis.iilang.Action(action, list));

			for (Percept percept: percepts.values()) {
				agent.addBelief(convertPercept(percept));
			}

			return true;
		} catch (ActException e) {
			if( e.getType() == ActException.NOTSPECIFIC) {
				System.out.println(agent.name() + ": Action Failure (NOTSPECIFIC): " + action + "(" + parameters +")");
			} else if( e.getType() == ActException.NOTREGISTERED ) {
				System.out.println(agent.name() + ": Action Failure (NOTREGISTERED): " + action + "(" + parameters +")");
			} else if( e.getType() == ActException.NOENTITIES ) {
				System.out.println(agent.name() + ": Action Failure (NOENTITIES): " + action + "(" + parameters +")");
			} else if( e.getType() == ActException.WRONGENTITY ) {
				System.out.println(agent.name() + ": Action Failure (WRONGENTITY): " + action + "(" + parameters +")");
			} else if( e.getType() == ActException.NOTSUPPORTEDBYENTITY ) {
				System.out.println(agent.name() + ": Action Failure (NOTSUPPORTEDBYENTITY): " + action + "(" + parameters +")");
			} else if( e.getType() == ActException.NOTSUPPORTEDBYENVIRONMENT ) {
				System.out.println(agent.name() + ": Action Failure (NOTSUPPORTEDBYENVIRONMENT): " + action + "(" + parameters +")");
			} else if( e.getType() == ActException.NOTSUPPORTEDBYTYPE ) {
				System.out.println(agent.name() + ": Action Failure (NOTSUPPORTEDBYTYPE): " + action + "(" + parameters +")");
			} else if( e.getType() == ActException.FAILURE ) {
				System.out.println(agent.name() + ": Action Failure (FAILURE): " + action + "(" + parameters +")");
			}
//			e.printStackTrace();
			agent.addBelief(new Predicate("failedAction", new ITerm[] { Term.newInstance(action) } ));
		} catch (NoEnvironmentException e) {
			System.out.println(agent.name() + ": Action Failed: " + action);
			agent.addBelief(new Predicate("failedAction", new ITerm[] { Term.newInstance(action) } ));
		} catch (AssertionError e) {
			System.out.println(agent.name() + ": Action Failed: " + action);
			agent.addBelief(new Predicate("failedAction", new ITerm[] { Term.newInstance(action) } ));
		}
		return false;
	}

	private Predicate convertPercept(Percept percept) {
		List<Parameter> parameters = percept.getParameters();
		ITerm[] terms = new ITerm[parameters.size()];
		for (int i=0; i < parameters.size(); i++) {
			Parameter parameter = parameters.get(i);
			if (Numeral.class.isInstance(parameter)) {
				terms[i] = Term.newInstance(((Numeral) parameter).getValue());
			} else {
				terms[i] = Term.newInstance(((Identifier) parameter).getValue());
			}
		}

		return new Predicate(percept.getName(), terms);
	}
	
	@SENSOR
	public List<Predicate> senseEIS() {
//		long start = System.currentTimeMillis();
		List<Predicate> list = new LinkedList<Predicate>();
		if (service == null) {
			list.add(new Predicate("eisState", new ITerm[] { Term.newInstance("disconnected") }));
		} else {
			list.add(new Predicate("eisState", new ITerm[] { Term.newInstance(service.getEnvironmentState()) }));

			if (service.isStartSupported()) {
				list.add(new Predicate("eisSupports", new ITerm[] { Term.newInstance("start") } ));
			}
		
			if (service.hasEnvironmentEvent()) {
				list.add(service.getNextEnvironmentEvent().toPredicate());
			}

			// Agent Specific Percepts
			for (String entity : associations) {
				list.add(new Predicate("associatedWithEntity", new ITerm[] { Term.newInstance(entity) }));
			}
			
			try {
				Collection<Collection<Percept>> percepts = service.getPercepts(agent).values();
				for (Collection<Percept> lst : percepts) {
					for (Percept percept: lst) {
						list.add(convertPercept(percept));
					}
				}
			} catch (PerceiveException e) {
			} catch (NoEnvironmentException e) {
			}
			
			List<Percept> percepts = service.getAgentEvents(agent);
			while (percepts!=null && !percepts.isEmpty()) {
				list.add(convertPercept(percepts.remove(0)));
			}
		}
		
//		long duration = System.currentTimeMillis() - start;
//		if (duration > 2000) System.out.println("[" + agent.name() + "] EIS duration: " + duration);
		return list;
	}
}
